#include <string>

#include "GTask.h"
#include "GError.h"
#include "GTaskResult.h"
#include "GTaskQueue.h"
#include "GWorkerThread.h"
#include "GSynchronizer.h"
#include "GTaskManager.h"
#include "GSemaphore.h"

namespace GEngine
{
	G_THREAD_LOCAL GWorkerThread* CurrentWorker = NULL;

	GUInt32 _stdcall ThreadEntry(void* Parameters)
	{
		CurrentWorker = static_cast<GWorkerThread*>(Parameters);

		return CurrentWorker->Start();
	}
	
	GWorkerThread::GWorkerThread()
	{
		Id = 0;
		Queue = NULL;
		Active = NULL;
		MainThread = false;
		CurrentResult = NULL;
	}
	
	GWorkerThread::~GWorkerThread()
	{
	}

	GSize GWorkerThread::GetId() const
	{
		return Id;
	}

	GTaskQueue* GWorkerThread::GetQueue() const
	{
		return Queue;
	}

	bool GWorkerThread::IsMainThread() const
	{
		return MainThread;
	}

	void GWorkerThread::GoIdle()
	{
		// Inform that we are going idle 
		Idle.Signal();

		// Wait to be active
		Active->Acquire();
	}

	// Entry method for worker threads
	// Main thread follows a different path
	GUInt32 GWorkerThread::Start()
	{
		GUtil::GError::DebugCheck(IsMainThread(), "Main thread cannot call this method.");
		GUtil::GError::DebugCheck(!Thread.IsCreated(), "Worker not created.");

		// Loop untill exit
		while (true)
		{
			// Wait idle
			GoIdle();

			// Check exit right after idle
			// Thread might be woken to exit
			if (mGTaskManager->CheckExiting())
			{
				break;
			}

			// Work until root task is compelte
			while (!mGTaskManager->CheckRootComplete())
			{
				// Execute available tasks
				Execute(nullptr);
			}
		}

		return 0;
	}

	// Executes tasks until they are done.
	// If MyResult is NULL executes all
	// Otherwise executed untill MyResult is set to complete
	// This lets waiting for a task in a task
	bool GWorkerThread::Execute(GTaskResult* MyResult)
	{
		GUtil::GError::DebugCheck(!MainThread && !Thread.IsCreated(), "Worker not created.");

		GTask* CurrentTask = NULL;
		GTaskResult* LastResult = NULL;

		do
		{
			// As long as we can get tasks
			while (Queue->Pop(&CurrentTask))
			{
				// Keep track of last result 
				LastResult = CurrentResult;
				CurrentResult = CurrentTask->Result;

				// Execute task
				GUInt32 Res = CurrentTask->Execute(Id);

				// Update Result
				// Worker directly modifies result
				// Since tasks can get deleted after execution
				CurrentResult->DecreaseRef();

				// Restore current result
				CurrentResult = LastResult;
			
				// Check if my local work is done
				if ((MyResult != NULL) && (GTaskResult::CheckComplete(*MyResult)))
				{
					return true;
				}
			}

			// Check if global work is done
			if (mGTaskManager->CheckRootComplete())
			{
				return true;
			}
			
			// Try to steal and go on execution
		} while (Queue->Steal());

		return false;
	}

	bool GWorkerThread::AttachMainThread(GTaskQueue* MyQueue)
	{
		Id = 0;
		MainThread = true;
		Queue = MyQueue;
		CurrentWorker = this;

		return true;
	}

	bool GWorkerThread::Create(GSize MyId, GTaskQueue* MyQueue, GThread::GSemaphore* ActiveSem, GThread::GEvent** OutIdle)
	{
		GUtil::GError::DebugCheck(Thread.IsCreated(), "Worker already created.");

		Idle.Create(false, true, nullptr);

		bool Result = Thread.Create(false, &ThreadEntry, this, 0);
		if (!Result)
		{
			return false;
		}

		Queue = MyQueue;
		Id = MyId;
		CurrentResult = NULL;
		MainThread = false;
		
		*OutIdle = &Idle;
		Active = ActiveSem;

		return true;
	}
	
	void GWorkerThread::Release()
	{
		GUtil::GError::DebugCheck(!Thread.IsCreated(), "Worker not created.");

		// Wait thread to exit before releasing
		Thread.Release(true);

		Idle.Release();

		Id = 0;
		Queue = NULL;
		Active = NULL;
		CurrentResult = NULL;
		MainThread = false;
	}

	GSize GWorkerThread::GetCurrentWorkerId()
	{
		return CurrentWorker->GetId();
	}

	GWorkerThread* GWorkerThread::GetCurrentWorker()
	{
		return CurrentWorker;
	}
}